{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Overriding"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As we saw in the lecture, classes that inherit from another class **inherit** the functionality from the parent class (and all parent classes up the chain).\n",
    "\n",
    "Let's look at what happens when we override the `__str__` method in a custom class (which remember inherits it from the `object` class):"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Person:\n",
    "    pass"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'<__main__.Person object at 0x7fbcb04c3908>'"
      ]
     },
     "execution_count": 2,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p = Person()\n",
    "str(p)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "What happened here is that `str()` tries to call a `__str__` method. Since the `Person` class does not define it, Python continues looking up the inheritance chain until it finds it - in this case it finds it in the `object` class, so it uses it."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now let's override the `__str__` method in the `Person` class:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Person:\n",
    "    def __str__(self):\n",
    "        return 'Person class'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "p = Person()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Person class'"
      ]
     },
     "execution_count": 5,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "str(p)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "What happens if we implement a `__repr__` method only, and still call the `str()` method:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Person:\n",
    "    def __repr__(self):\n",
    "        return 'Person()'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "p = Person()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Person()'"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "str(p)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As you can see it ended calling `__repr__` **in the Person class**, even though we did not have a `__str__` method defined - that's because `objects` delegates `str` to `__repr__` which in turn will find it in our class."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "As we discussed in the lecture, in an inheritance chain we have to be very aware of how overrides are handled."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's create a simple chain:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Shape:\n",
    "    def __init__(self, name):\n",
    "        self.name = name\n",
    "        \n",
    "    def info(self):\n",
    "         return f'Shape.info called for Shape({self.name})'\n",
    "    \n",
    "    def extended_info(self):\n",
    "        return f'Shape.extended_info called for Shape({self.name})'\n",
    "    \n",
    "class Polygon(Shape):\n",
    "    def __init__(self, name):\n",
    "        self.name = name  # we'll come back to this later in the context of using the super()\n",
    "        \n",
    "    def info(self):\n",
    "        return f'Polygon info called for Polygon({self.name})'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "p = Polygon('square')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Polygon info called for Polygon(square)'"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p.info()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "But if we call `extended_info`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Shape.extended_info called for Shape(square)'"
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p.extended_info()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "That makes sense, it uses `extended_info` in the superclass - but now let's add a twist - let's have `extended_info` in the `Shape` class also call `info`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Shape:\n",
    "    def __init__(self, name):\n",
    "        self.name = name\n",
    "        \n",
    "    def info(self):\n",
    "         return f'Shape.info called for Shape({self.name})'\n",
    "    \n",
    "    def extended_info(self):\n",
    "        return f'Shape.extended_info called for Shape({self.name})', self.info()\n",
    "    \n",
    "class Polygon(Shape):\n",
    "    def __init__(self, name):\n",
    "        self.name = name  # we'll come back to this later in the context of using the super()\n",
    "        \n",
    "    def info(self):\n",
    "        return f'Polygon.info called for Polygon({self.name})'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "metadata": {},
   "outputs": [],
   "source": [
    "p = Polygon('Square')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Polygon.info called for Polygon(Square)'"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "p.info()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "That works the same as before. But what about `extended_info`? Remember it will use the definition in `Shape`, which in turn calls `info`. Keep in mind that `self` in that context refers to `p` - a `Polygon` class which overrides `info`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "('Shape.extended_info called for Shape(Square)', 'Polygon.info called for Polygon(Square)')\n"
     ]
    }
   ],
   "source": [
    "print(p.extended_info())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And this is the same mechanism that results in `str(Person)` ending up calling the `__repr__` method in the `Person` class instead of the `__repr__` method in the `object` class which would have just printed out the name and memory address of the `Person` instance."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In fact we can see how this happens exactly this way:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Person:\n",
    "    def __str__(self):\n",
    "        return 'Person.__str__ called'\n",
    "    \n",
    "class Student(Person):\n",
    "    def __repr__(self):\n",
    "        return 'Student.__repr__ called'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [],
   "source": [
    "s = Student()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Person.__str__ called'"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "str(s)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Student.__repr__ called'"
      ]
     },
     "execution_count": 20,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "repr(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And if we now have `__str__` delegate to `__repr__` instead:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 21,
   "metadata": {},
   "outputs": [],
   "source": [
    "class Person:\n",
    "    def __str__(self):\n",
    "        print('Person.__str__ called')\n",
    "        return self.__repr__()\n",
    "    \n",
    "class Student(Person):\n",
    "    def __repr__(self):\n",
    "        return 'Student.__repr__ called'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 22,
   "metadata": {},
   "outputs": [],
   "source": [
    "s = Student()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 23,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Person.__str__ called\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "'Student.__repr__ called'"
      ]
     },
     "execution_count": 23,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "str(s)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Student.__repr__ called'"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "repr(s)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Basically just keep track of which instance the methods are bound to and always start working you way from there to find the \"closest\" relevant method."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
